#include "backdrop_layer.h"
#include "backdrop_scroll.h"
#include "decal.h"

#include "camera/fb_camera.h"
#include "util/common_macros.h"
#include "util/mmgr.h"

using namespace backdrop;

int BackdropLayer::m_ScreenW = 0;
int BackdropLayer::m_ScreenH = 0;

BackdropLayer::BackdropLayer(BackdropScroll* pBackdrop) :
	m_pBackdrop(pBackdrop),
	m_ParallaxFactor(1.0f),
	m_Width(0),
	m_Height(0),
	m_UVScroll(false),
	m_IsVisible(true),
	m_IsEnabled(true)
{
	if (!m_ScreenW || !m_ScreenH) {
		m_ScreenW = pBackdrop->GetCamera()->GetWidth();
		m_ScreenH = pBackdrop->GetCamera()->GetHeight();
	}
}

BackdropLayer::~BackdropLayer()
{
	ClearDecals();
}

bool BackdropLayer::Create(const std::string& File, const bool& UVScroll, const float& ParallaxFactor)
{
	bool bImageLoaded;

	m_Name = File;
	if (m_Name.empty())
		m_Sprite.Invalidate();
	else
		m_Sprite.Create(m_Name);

	bImageLoaded = m_Sprite.IsValid();

	if (bImageLoaded && UVScroll && m_Sprite.GetSpriteInst() && m_Sprite.GetImage())
	{
		// we want the graphic, not the sprite instance, to be able to control UV
		if (!m_Sprite.Create(m_Sprite.GetImage()->GetFile())) return false;
	}

	m_UVScroll = UVScroll;
	m_ParallaxFactor = ParallaxFactor;

	m_Width = bImageLoaded ? m_Sprite.GetImage()->GetWidth() : 0;
	m_Height = bImageLoaded ? m_Sprite.GetImage()->GetHeight() : 0;

	if (m_Sprite.GetSpriteInst()) {
		m_Width -= (int)m_Sprite.GetSpriteInst()->ClipX;
		m_Height -= (int)m_Sprite.GetSpriteInst()->ClipY;
	}

	// set default size
	if (!bImageLoaded)
	{
		m_Size.x = m_Size.y = 0.0f;
	}
	if (UVScroll)
	{
		m_Size.x = (m_Width > m_ScreenW) ? 1.0f : float(m_Width) / float(m_ScreenW);
		m_Size.y = (m_Height > m_ScreenH) ? 1.0f : float(m_Height) / float(m_ScreenH);
	}
	else
	{
		m_Size.x = float(m_Width) / float(m_ScreenW);
		m_Size.y = float(m_Height) / float(m_ScreenH);
	}

	// set default UV
	if (bImageLoaded && UVScroll)
	{
		m_Sprite.m_UV1.x = 0.0f;
		m_Sprite.m_UV1.y = 0.0f;
		m_Sprite.m_UV2.x = (m_Width > m_ScreenW) ? float(m_ScreenW) / float(m_Width) : 1.0f;
		m_Sprite.m_UV2.y = (m_Height > m_ScreenH) ? float(m_ScreenH) / float(m_Height) : 1.0f;
	}
	else
	{
		m_Sprite.m_UV1 = math::Vec2(0.0f, 0.0f);
		m_Sprite.m_UV2 = math::Vec2(1.0f, 1.0f);
	}

	if (bImageLoaded) m_Sprite.Setup(m_Pos, m_Size);

	return true;
}

void BackdropLayer::Setup(const math::Vec2& pos)
{
	Setup(pos, m_Size);
}

void BackdropLayer::Setup(const math::Vec2& pos, const math::Vec2& size)
{
	m_Pos = pos;
	m_Size = size;

	if (m_UVScroll) {
		if ((m_Pos.x + m_Size.x) > 1.0f) m_Size.x = 1.0f - m_Pos.x;
		if ((m_Pos.y + m_Size.y) > 1.0f) m_Size.y = 1.0f - m_Pos.y;

		if (m_Size.x > 1.0f) m_Size.x = 1.0f;
		if (m_Size.y > 1.0f) m_Size.y = 1.0f;

		if ((m_Width < m_ScreenW) && (m_Size.x > (float(m_Width) / float(m_ScreenW)))) m_Size.x = 1.0f;
		if ((m_Height < m_ScreenH) && (m_Size.y > (float(m_Height) / float(m_ScreenH)))) m_Size.y = 1.0f;

		UpdateUV();
	}

	m_Sprite.Setup(m_Pos, m_Size);
}

void BackdropLayer::Scroll(const math::Vec2& pos, bool bRelative)
{
	if (m_UVScroll)
	{
		math::Vec2 Dim;
		GetVisibleDim(Dim);

		if (bRelative)
		{
			m_Offset.x += pos.x * Dim.x * m_ParallaxFactor;
			m_Offset.y += pos.y * Dim.y * m_ParallaxFactor;
		}
		else
		{
			m_Offset.x = pos.x * Dim.x * m_ParallaxFactor;
			m_Offset.y = pos.y * Dim.y * m_ParallaxFactor;

			if (!m_pBackdrop->ClampToLeft()) m_Offset.x += (1.0f - Dim.x);
			if (!m_pBackdrop->ClampToTop()) m_Offset.y += (1.0f - Dim.y);
		}

		UpdateUV();
	}
	else
	{
		if (bRelative)
			m_Offset += pos * m_ParallaxFactor;
		else
			m_Offset = pos * m_ParallaxFactor;
	}
}

void BackdropLayer::Tick(float dt)
{
	if (m_Sprite.IsValid()) m_Sprite.Tick(dt);

	for (unsigned int i = 0; i < m_Decals.size(); i++) {
		m_Decals[i]->Tick(dt);
		//if (m_Decals[i]->AffectedByWind()) m_Decals[i]->Move();
	}
}

void BackdropLayer::Render()
{
	if (!m_IsVisible) return;

	if (m_UVScroll && m_Sprite.IsValid())
	{
		m_Sprite.m_Color = m_IsEnabled ? EnabledColor() : DisabledColor();
		RenderSprite();
	}
	else if (m_Sprite.IsValid())
	{
		math::Vec2 Pos, Size;

		GetRealSize(Size);
		Pos = m_Pos - m_Offset;

		if (!m_pBackdrop->ClampToLeft()) Pos.x += (1.0f - Size.x);
		if (!m_pBackdrop->ClampToTop()) Pos.y += (1.0f - Size.y);

		if (((Pos.x + m_Size.x) < 0.0f) ||
			(Pos.x > 1.0f) ||
			((Pos.y + m_Size.y) < 0.0f) ||
			(Pos.y > 1.0f))
			return; // out of bounds

		m_Sprite.Setup(Pos, m_Size, 0.0f, 1.0f, m_IsEnabled ? EnabledColor() : DisabledColor());

		/* -- why does RenderClipped stretch image under certain circumstances???
		if ((Pos.x < 0.0f) || ((Pos.x + m_Size.x) > 1.0f) || (Pos.y < 0.0f) || ((Pos.y + m_Size.y) > 1.0f))
		{
			math::Vec2 ClipMin, ClipMax;

			ClipMin.x = UTIL_HIGH(Pos.x, 0.0f);
			ClipMin.y = UTIL_HIGH(Pos.y, 0.0f);
			ClipMax.x = UTIL_LOW((Pos.x + m_Size.x), 1.0f);
			ClipMax.y = UTIL_LOW((Pos.y + m_Size.y), 1.0f);

			m_Sprite.RenderClipped(ClipMin, ClipMax);
		}
		else*/
			RenderSprite();
	}

	RenderDecals();
}

void BackdropLayer::RenderDecals()
{
	for (unsigned int i = 0; i < m_Decals.size(); i++) {
		m_Decals[i]->SetColor( m_IsEnabled ? EnabledColor() : DisabledColor() );
		m_Decals[i]->Render();
	}
}

void BackdropLayer::ResetLayer()
{
	Scroll(m_pBackdrop->GetOffset(), false);
	ResetDecals();
}

void BackdropLayer::ResetDecals()
{
	for (unsigned int i = 0; i < m_Decals.size(); i++) {
		m_Decals[i]->SetPos(m_Decals[i]->GetStartPos());
		m_Decals[i]->SetParallaxFactor(m_ParallaxFactor);
	}
}

void BackdropLayer::AddDecalSprite(Decal* pDecal)
{
	pDecal->SetParallaxFactor(m_ParallaxFactor);

	for (unsigned int i = 0; i < m_Decals.size(); i++)
	{
		if (m_Decals[i]->GetDecalType() > pDecal->GetDecalType()) {
			m_Decals.insert(m_Decals.begin()+i, pDecal);
			return;
		}
	}

	m_Decals.push_back(pDecal);
}

Decal* BackdropLayer::GetDecalSprite(const math::Vec2& Pos)
{
	if (!m_IsVisible || !m_IsEnabled) return NULL;

	for (int i = (int)(m_Decals.size() - 1); i >= 0; i--)
	{
		if (m_Decals[i]->GetIsVisible() && m_Decals[i]->HitTest(Pos))
			return m_Decals[i];
	}

	return NULL;
}

bool BackdropLayer::DropDecalSprite(Decal* pDecal, bool bDelete)
{
	for (unsigned int i = 0; i < m_Decals.size(); i++)
	{
		if (pDecal == m_Decals[i]) {
			m_Decals.erase(m_Decals.begin() + i);
			if (bDelete)
				UTIL_SAFE_DELETE(pDecal);
			return true;
		}
	}

	return false;
}

void BackdropLayer::ClearDecals()
{
	for (unsigned int i = 0; i < m_Decals.size(); i++)
		UTIL_SAFE_DELETE(m_Decals[i]);
	m_Decals.clear();
}

void BackdropLayer::LoadDecals(LuaPlus::LuaObject& Data)
{
	if (Data.IsTable())
	{
		int DecalC = Data.GetTableCount();
		for (int i = 0; i < DecalC; i++)
		{
			LuaPlus::LuaObject DecalData = Data.GetByIndex(i+1);
			std::string Name = script::ScriptHelper::GetTableString(DecalData, "Name");
			math::Vec2 Pos = script::ScriptHelper::GetTableVec2(DecalData, "Pos");

			if (!Name.empty()) {
				if (Decal* pDecal = m_pBackdrop->CreateDecalSprite(Name, Pos))
					AddDecalSprite(pDecal);
			}
		}
	}
}

void BackdropLayer::SaveDecals(script::WriteLua& Output)
{
	if (m_Decals.empty()) return;

	Output.StartTable("Decals");

	for (unsigned int i = 0; i < m_Decals.size(); i++)
	{
		Output.StartTable();
		Output.WritePair("Name", m_Decals[i]->GetTypeText());
		Output.WritePair("Pos", m_Decals[i]->GetStartPos());
		Output.CloseTable();
	}

	Output.CloseTable();
}

void BackdropLayer::UpdateUV()
{
	math::Vec2 UV1, UV2, UVSize;

	GetVisibleDim(UVSize);

	if (UVSize.x < 1.0f)
	{
		UV1.x = m_Offset.x;
		UV2.x = UV1.x + UVSize.x;

		if (UV2.x > 1.0f) {
			UV1.x = 1.0f - UVSize.x;
			UV2.x = 1.0f;
		}
		else if (UV1.x < 0.0f) {
			UV2.x = UVSize.x;
			UV1.x = 0.0f;
		}
	}
	else
	{
		UV1.x = 0.0f;
		UV2.x = 1.0f;
	}

	if (UVSize.y < 1.0f)
	{
		UV1.y = m_Offset.y * UVSize.y;
		UV2.y = UV1.y + UVSize.y;

		if (UV2.y > 1.0f) {
			UV1.y = 1.0f - UVSize.y;
			UV2.y = 1.0f;
		}
		else if (UV1.y < 0.0f) {
			UV2.y = UVSize.y;
			UV1.y = 0.0f;
		}
	}
	else
	{
		UV1.y = 0.0f;
		UV2.y = 1.0f;
	}

	m_Sprite.m_UV1 = UV1;
	m_Sprite.m_UV2 = UV2;
}

void BackdropLayer::GetRealSize(math::Vec2& size)
{
	size.x = float(m_Width) / float(m_ScreenW);
	size.y = float(m_Height) / float(m_ScreenH);
}

void BackdropLayer::GetVisibleDim(math::Vec2& size)
{
	size.x = (m_ScreenW < m_Width) ? float(m_ScreenW) / float(m_Width) : 1.0f;
	size.y = (m_ScreenH < m_Height) ? float(m_ScreenH) / float(m_Height) : 1.0f;
}

void BackdropLayer::RenderSprite()
{
	if ((m_Sprite.m_Pos.x > 1.0f) || (m_Sprite.m_Pos.x < -m_Sprite.m_Size.x) ||
		(m_Sprite.m_Pos.y > 1.0f) || (m_Sprite.m_Pos.y < -m_Sprite.m_Size.y))
		return;

	// layers are usually big; might warrant using RenderClipped instead
	if ((m_Sprite.m_Pos.x >= 0.0f) && (m_Sprite.m_Pos.x <= (1.0f - m_Sprite.m_Size.x)) &&
		(m_Sprite.m_Pos.y >= 0.0f) && (m_Sprite.m_Pos.y <= (1.0f - m_Sprite.m_Size.y)))
	{
		m_Sprite.Render();
	}
	else
	{
		math::Vec2 ClipMin, ClipMax;

		ClipMin.x = UTIL_HIGH(0.0f, m_Sprite.m_Pos.x);
		ClipMin.y = UTIL_HIGH(0.0f, m_Sprite.m_Pos.y);
		ClipMax.x = UTIL_LOW(1.0f, (m_Sprite.m_Pos.x + m_Sprite.m_Size.x));
		ClipMax.y = UTIL_LOW(1.0f, (m_Sprite.m_Pos.y + m_Sprite.m_Size.y));

		m_Sprite.RenderClipped(ClipMin, ClipMax);
	}
}
